package org.leumasjaffe.initiativeTracker.operator;


import java.io.IOException;
import java.util.Hashtable;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.leumasjaffe.initiativeTracker.Constants;
import org.leumasjaffe.initiativeTracker.entity.Character;
import org.leumasjaffe.initiativeTracker.entity.NPC;
import org.leumasjaffe.initiativeTracker.entity.PC;

import org.leumasjaffe.util.io.SerializeString;
import org.leumasjaffe.util.io.PortableScanner;

/**
 * Class to handle Character generation
 * @author Sam Jaffe
 *
 */
@NonNullByDefault
public class CharacterGenerator {

	private int id = 0;
	
	private UnitManager root;

	private Hashtable<String, Integer> countETable = new Hashtable<String, Integer>();
	private Hashtable<String, Integer> countATable = new Hashtable<String, Integer>();
	private Hashtable<String, NPC> storedMobs;
	
	/**
	 * Creates the CharacterGenerator, ensuring that the id value is a the right point
	 * and that the NPC counters are at the right points too
	 * @param allPieces The list of characters
	 */
	public CharacterGenerator(InitiativeController tracker) {
		for (CharacterLinker cc : tracker.list.getList()) {
			Character c = cc.getCharacter();
			if (c.getId() >= this.id)
				this.id = c.getId()+1;
			//Make sure that the id value is as high as it needs to be for equality checks
			if (c instanceof NPC)
				account((NPC) c);
		}
		if (Constants.isDM && Constants.client) {
			try {
				this.storedMobs = (Hashtable<String, NPC>) SerializeString.fromFile(tracker.id + ".mobs");
			} catch (IOException e) {
				this.storedMobs = new Hashtable<String, NPC>();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		this.root = tracker.root;
	}

	/**
	 * Remakes the given character, returning an effective copy
	 * @param c The (NPC) Character to be cloned
	 * @return A copy of the given Character
	 */
	public NPC remake(Character c) {
		NPC temp = new NPC(((NPC)c).isAllied(), this.id); //Copy in the NPC's allied status and give it a new id
		this.id++; //Increment the id
				
		temp.setName(((NPC) c).getShortName()); //Give temp the short name of the given character
		temp.setDis(c.getDis());

		count(temp); //Get and modify the count of temp
		
		//Copy in the rest of the data
		temp.setHp(c.getMaxHP());
		temp.setMaxHP(c.getMaxHP());
		temp.setAc(c.getAc());
		temp.setFort(c.getFort());
		temp.setRef(c.getRef());
		temp.setWill(c.getWill());
		
		return temp; //Return the NPC
	}

	/**
	 * Creates a new Character from user input
	 * @param um The UnitManager top class that receives the Character data
	 * @return Return the completed Character
	 */
	@SuppressWarnings("null")
	public Character createChar() {
		
		Character c = null;
				
		System.out.print("Is this a Player, Ally, or Enemy: ");
		String inputLine;
		
		while (c == null) {
			inputLine = PortableScanner.nextLine(); //Read the next line
			
			if (inputLine.equalsIgnoreCase("P")) {
				c = new PC(this.id++);
				System.out.println("Creating new Player");
			}
			else if (inputLine.equalsIgnoreCase("A")) {
				c = new NPC(this.id++, true);
				System.out.println("Creating new Ally");
			}
			else if (inputLine.equalsIgnoreCase("E")) {
				c = new NPC(this.id++, false);
				System.out.println("Creating new Foe");
				
			}
			else if (!inputLine.equals(""))
				System.out.print("Please input in the form of P, A, or E:");
		}

		boolean close = false; //Flag for we can lookup the Character (NPC) in the storedMobs hashtable
		
		System.out.print("NAME: "); //Prompt for the Character's name
		inputLine = PortableScanner.nextLine(); 
		c.setName(inputLine);

		//Check that c is an NPC and if it is, if its name is in the storedMobs hashtable
		if (c instanceof NPC && getStoredMobs().containsKey(((NPC) c).getShortName())) {
			c = getStoredMobs().get(((NPC) c).getShortName());
			c.setId(this.id-1);
			close = true; //If we succeeded, mark close as true
		}

		if (c instanceof PC){ //If we have a PlayerCharacter
			System.out.print("Player Name: ");
			inputLine = PortableScanner.nextLine(); //Prompt and read in the player's name
			((PC) c).setPlayer(inputLine);
			int x = PortableScanner.nextInt("Healing Surges");
			//Prompt and read in the number of healing surges
			((PC) c).setSurgeMax(x);
			((PC) c).setSurgeLeft(x);
		}

		else if (c instanceof NPC){
			this.root.setNum(PortableScanner.nextInt("How Many"));
			count((NPC)c); //Count up the NPC
			if (close) //If we pulled the NPC from the hashtable, we're done
				return c;
		}

		//Otherwise...
		
		int HP = PortableScanner.nextInt("HP"); //Read in HP
		c.setMaxHP(HP);
		c.setHp(HP);

		c.setAc(PortableScanner.nextInt("AC")); //Read in AC

		c.setFort(PortableScanner.nextInt("FORT")); //Read in Fortitude

		c.setRef(PortableScanner.nextInt("REF")); //Read in reflex

		c.setWill(PortableScanner.nextInt("WILL")); //Read in will

		PortableScanner.nextLine(); //Move to a new line on the console
		
		if (c instanceof NPC) {//If we are still in the function and c is an NPC...
			getStoredMobs().put(((NPC) c).getShortName(), (NPC) c);
			SerializeString.toFile(this.root.tracker.id + ".mobs", this.storedMobs);
			//Store it in the hashtable
		}

		PortableScanner.nextLine();
		
		return c;
	}

	/**
	 * Accounts for a given NPC without changing its count value
	 * @param c The NPC to account for
	 */
	private void account(NPC c) {
		Hashtable <String,Integer> countTable;
		if (!c.isAllied()) //If the NPC is an Enemy
			countTable = this.countETable; //use the Enemy table
		else if (c.isAllied()) //If NPC is allied
			countTable = this.countATable; //Use allied table
		else
			throw new AssertionError("You've just broken booleans, congrats");
		//Otherwise, you've broken logic

		if (countTable.containsKey(c.getShortName())) {
			//Check is the table has the NPC's name
			int i = countTable.get(c.getShortName()); //Get the count
			if (c.getCount() >= i) //Only increment if our count is not less
				countTable.put(c.getShortName(), c.getCount()+1);
		}
		else {
			countTable.put(c.getShortName(), 2); //If we don't have the NPC in the table,
			//  add it and increment
		}
	}

	/**
	 * Counts a given NPC, incrementing the count table's entry for
	 * that NPC and applying the appropriate count to the NPC
	 * @param c The NPC to account for
	 */
	private void count(NPC c) {
		Hashtable <String,Integer> countTable;
		if (!c.isAllied()) //If the NPC is an Enemy
			countTable = this.countETable; //use the Enemy table
		else if (c.isAllied()) //If NPC is allied
			countTable = this.countATable; //Use allied table
		else
			throw new AssertionError("You've just broken booleans, congrats"); 
		//Otherwise, you've broken logic

		if (countTable.containsKey(c.getShortName())) { //Check is the table has the NPC's name
			int i = countTable.get(c.getShortName()); //Get the count
			c.setCount(i); //Set c's count to i
			countTable.put(c.getShortName(), i+1); //Increment this value
		}
		else { //Otherwise
			c.setCount(1); // Set the count at 1
			countTable.put(c.getShortName(), 2); //Put the value at 2
		}
	}

	@SuppressWarnings("null")
	public Hashtable<String, NPC> getStoredMobs() {
		return this.storedMobs;
	}

	public void setId(int id) {
		this.id = id;
	}

}
